/*
 * 文件名称：TokenFilter.java
 * 系统名称：[系统名称]
 * 模块名称：[模块名称]
 * 软件版权：Copyright (c) 2011-2019, liming20110711@163.com All Rights Reserved.
 * 功能说明：[请在此处输入功能说明]
 * 开发人员：Rushing0711
 * 创建日期：20190109 05:05
 * 修改记录：
 * <Version>        <DateSerial>        <Author>        <Description>
 * 1.0.0            20190109-01         Rushing0711     M201901090505 新建文件
 ********************************************************************************/
package com.ishanshan.gateway.filter;

import com.beust.jcommander.internal.Lists;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import com.google.common.collect.Maps;
import com.ishanshan.gateway.auth.LoginData;
import com.ishanshan.gateway.cache.CacheUtil;
import com.ishanshan.gateway.exception.GatewayException;
import com.ishanshan.gateway.exception.GatewayStatus;
import com.ishanshan.gateway.jwt.JwtAuthConstants;
import com.ishanshan.gateway.jwt.JwtTokenUtil;
import com.ishanshan.gateway.util.SysNameUtil;
import com.ishanshan.util.JacksonUtils;
import com.ishanshan.util.SignUtils;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.netflix.zuul.http.ServletInputStreamWrapper;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;


/**
 * 权限拦截.
 *
 * <p>创建时间: <font style="color:#00FFFF">20190109 05:17</font><br>
 * [请在此输入功能详述]
 *
 * @author Rushing0711
 * @version 1.0.0
 * @since 1.0.0
 */
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends ZuulFilter {


    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private CacheUtil cacheUtil;

    private String[] whiteList={};

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
    }

    private String MD5_KEY="kje4sw2tgrpac97mw5g6plaunfvlus0w";

    @Override
    public boolean shouldFilter() {//如果登陆请求不验证token
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        String checkTokenHead = request.getHeader("checkToken")==null?request.getParameter("checkToken"):request.getHeader("checkToken");
        log.info("请求地址为:【{}】;checkToken:【{}】",request.getRequestURL(),checkTokenHead);
        if (JwtAuthConstants.isAuthLogin(request)) {
            return false;
        }else if("0".equals(checkTokenHead)){
            return false;
        }else if(JwtAuthConstants.isWhiteList(whiteList,request)){
            log.info("请求:【{}】是白名单",request.getRequestURI());
            return false;
        }
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        String head = request.getHeader("checkType")==null?"jwt":request.getHeader("checkType");
        log.info("请求地址为:【{}】,checkType:【{}】",request.getRequestURL(),head);
        if("md5".equals(head)){
            this.md5Verify(requestContext);
        }else if("jwt".equals(head)){
            String jwtToken = jwtTokenUtil.fetchToken(request);
            if(jwtTokenUtil.validateToken(jwtToken)){
                String contentType = request.getHeader("Content-Type");
                LoginData loginData = jwtTokenUtil.getLoginData(jwtToken);
                if(contentType != null && contentType.contains("multipart/form-data")){
                    this.addQueryParams(loginData);
                }else {
                    this.combineRequestAntAuthSession(loginData);
                }
            }else{
                log.info("Token校验失败:Token为【{}】",jwtToken);
                requestContext.setSendZuulResponse(false);
                throw new GatewayException(GatewayStatus.GATEWAY_PRE_TOKEN_INVALID);
            }
        }else{
            requestContext.setSendZuulResponse(false);
            throw new GatewayException(GatewayStatus.GATEWAY_ERROR);
        }
        return null;
    }

    private void md5Verify(RequestContext requestContext){
        try{
            HttpServletRequest request = requestContext.getRequest();
            InputStream in = request.getInputStream();
            String requestBody = StreamUtils.copyToString(in, Charset.forName(JwtAuthConstants.DEFAULT_CHARSET.name()));
            if("".equals(requestBody)){
                requestBody="{}";
            }
            JSONObject reqJson = JSONObject.fromObject(requestBody);
            reqJson.put("key", this.MD5_KEY);
            reqJson.put("sign_type", "md5");
            if(!SignUtils.verify(reqJson,"")){
                requestContext.setSendZuulResponse(false);
                throw new GatewayException(GatewayStatus.GATEWAY_SIGN_ERROR);
            }
            final byte[] reqBodyBytes = requestBody.getBytes();
            requestContext.setRequest(
                    new HttpServletRequestWrapper(request) {
                        @Override
                        public ServletInputStream getInputStream() throws IOException {
                            return new ServletInputStreamWrapper(reqBodyBytes);
                        }

                        @Override
                        public int getContentLength() {
                            return reqBodyBytes.length;
                        }

                        @Override
                        public long getContentLengthLong() {
                            return reqBodyBytes.length;
                        }

                        @Override
                        public String getMethod() {
                            return "POST";
                        }

                        @Override
                        public Enumeration<String> getHeaderNames() {
                            Enumeration<String> result =  super.getHeaderNames();
                            Set<String> set = new HashSet<>();
                            while(result.hasMoreElements()){
                                set.add(result.nextElement());
                            }
                            set.add("Content-Type");
                            return Collections.enumeration(set);
                        }

                        @Override
                        public Enumeration<String> getHeaders(String name) {
                            if("Content-Type".equals(name)){
                                Set<String> set = new HashSet<>();
                                set.add("application/json");
                                return Collections.enumeration(set);
                            }
                            return super.getHeaders(name);
                        }
                    });
        }catch (IOException e){
            throw new RuntimeException("获取参数出错");
        }


    }

    private void addQueryParams(LoginData loginData){

        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        String url = request.getRequestURI();
        Map<String, List<String>> qp = new HashMap<>();
        Map<String,String> authJson = Maps.newHashMap();
        authJson.put("tenantId",loginData.getTenantId());
        authJson.put("orgId",loginData.getOrgId());
        authJson.put("uid",loginData.getUserId());
//        Map<String,String> session = cacheUtil.getSession(loginData,SysNameUtil.getSysName());
//        if(session != null){
//            authJson.putAll(session);
//        }
        authJson.forEach(new BiConsumer<String, String>() {
            @Override
            public void accept(String s, String s2) {
                qp.put(s, Lists.newArrayList(s2));
            }
        });
        requestContext.setRequestQueryParams(qp);
    }

    private void combineRequestAntAuthSession(LoginData loginData) {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        String url = request.getRequestURI();
        try {
            InputStream in = requestContext.getRequest().getInputStream();
            Map<String,String> authJson = Maps.newHashMap();
            authJson.put("tenantId",loginData.getTenantId());
            authJson.put("orgId",loginData.getOrgId());
            authJson.put("uid",loginData.getUserId());
            Map<String,String> session = cacheUtil.getSession(loginData,SysNameUtil.getSysName());
            if(session != null){
                authJson.putAll(session);
            }
            String requestBody = StreamUtils.copyToString(in, Charset.forName(JwtAuthConstants.DEFAULT_CHARSET.name()));
            if("".equals(requestBody)){
                requestBody="{}";
            }
            log.info("【网关前置过滤】合并参数,方法执行前: url=【{}】, requestBody=【{}】", url, requestBody);
            JSONObject reqJson = JSONObject.fromObject(requestBody);
            Enumeration<String> enumeration = request.getParameterNames();
            while (enumeration.hasMoreElements()){
                String paramKey = enumeration.nextElement();
                reqJson.put(paramKey,request.getParameter(paramKey));
            }
            for (Object authKey : authJson.keySet()) {
                if (reqJson.containsKey(authKey)) {
                    log.warn("【网关前置过滤】合并参数时发生覆盖, url=【{}】, key=【{}】", url, authKey);
                    reqJson.put(authKey, authJson.get(authKey));
                } else {
                    reqJson.put(authKey, authJson.get(authKey));
                }
            }
            String newRequestBody = reqJson.toString();
            log.info("【网关前置过滤】合并参数,方法执行后: url=【{}】, newRequestBody=【{}】", url, newRequestBody);
            final byte[] reqBodyBytes = newRequestBody.getBytes();
            requestContext.setRequest(
                    new HttpServletRequestWrapper(request) {
                        @Override
                        public ServletInputStream getInputStream() throws IOException {
                            return new ServletInputStreamWrapper(reqBodyBytes);
                        }

                        @Override
                        public int getContentLength() {
                            return reqBodyBytes.length;
                        }

                        @Override
                        public long getContentLengthLong() {
                            return reqBodyBytes.length;
                        }

                        @Override
                        public String getMethod() {
                            return "POST";
                        }

                        @Override
                        public Enumeration<String> getHeaderNames() {
                            Enumeration<String> result =  super.getHeaderNames();
                            Set<String> set = new HashSet<>();
                            while(result.hasMoreElements()){
                                set.add(result.nextElement());
                            }
                            set.add("Content-Type");
                            return Collections.enumeration(set);
                        }

                        @Override
                        public Enumeration<String> getHeaders(String name) {
                            if("Content-Type".equals(name)){
                                Set<String> set = new HashSet<>();
                                set.add("application/json");
                                return Collections.enumeration(set);
                            }
                            return super.getHeaders(name);
                        }
                    });
        } catch (IOException e) {
            log.error("【网关前置过滤】更新请求信息失败,执行失败!", e);
            throw new GatewayException(GatewayStatus.GATEWAY_PRE_COMBINE_REQUEST_BODY_ERROR);
        }
    }

}
